﻿// Desktop class, (c)2010 Knihovnik
// This file is licensed under GNU GPL license v2, see more at http://www.gnu.org/licenses/gpl-2.0.html
namespace vKapse.VirtualDesktop.Core
{

	#region Using

	using System;
	using System.Collections.Generic;
	using System.ComponentModel;
	using System.Diagnostics;
	using System.Drawing;
	using System.IO;
	using System.Runtime.InteropServices;
	using System.Text;
	using System.Windows.Forms;
	using System.Xml.Serialization;
	using Microsoft.Win32;

	#endregion Using

	/// <summary>
	/// Represents virtual desktop
	/// </summary>
	[Serializable]
	public class Desktop
	{

		#region Win32

		[DllImport("user32.dll")]
		static extern bool IsWindow(IntPtr hWnd);
		[DllImport("user32.dll")]
		static extern bool IsWindowVisible(IntPtr hWnd);

		[DllImport("user32.dll")]
		[return: MarshalAs(UnmanagedType.Bool)]
		static extern bool EnumThreadWindows(uint dwThreadId, EnumThreadDelegate lpfn, IntPtr lParam);
		public delegate bool EnumThreadDelegate(IntPtr hWnd, IntPtr lParam);

		[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
		static extern int GetWindowTextLength(IntPtr hWnd);

		[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
		static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);

		[DllImport("user32.dll", CharSet = CharSet.Auto)]
		public static extern int SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni);

		#endregion Win32

		#region Members

		private Dictionary<IntPtr, DesktopWindow> m_windows;
		private string m_name; // Name of this virtual desktop
		private string m_wallpaper; // Wallpaper of this virtual desktop
		private EnumThreadDelegate m_deleg;
		private object m_tag; // Tag
		private Keys m_hotkey; // Globla shortcut key
		private string m_currentProcess; // Helper variable
		private Bitmap m_thumbnail; // This desktop thumbnail
		private int m_number;
		private static int _count = 0;
		private static string _currentWallpaper; // Helper variable (for switching backgrounds)

		#endregion Members

		#region Constructors

		/// <summary>
		/// Basic protected constructor
		/// </summary>
		protected Desktop()
		{
			KeysConverter kc = new KeysConverter();
			this.m_hotkey = (Keys.Alt | (Keys)(49 + _count++));// Get Alt + Number hotkey
			HotKeyWrapper.Register(this.m_hotkey);
			HotKeyWrapper.HotKeyPressed += new KeyEventHandler(HotKeyWrapper_HotKeyPressed);
			
			this.m_windows = new Dictionary<IntPtr, DesktopWindow>();
			this.m_wallpaper = Desktop.GetCurrentWallpaper();
			_currentWallpaper = this.m_wallpaper;

			this.m_number = _count;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="name">Desktop name</param>
		internal Desktop(string name)
			: this()
		{
			this.m_name = name;
		}

		#endregion Constructors

		#region Properties

		/// <summary>
		/// Gets desktop thumbnail
		/// </summary>
		[XmlIgnore]
		internal Bitmap Thumbnail
		{
			get
			{
				if (this.m_thumbnail == null)
				{
					this.CreateThumbnail();
				}
				return this.m_thumbnail;
			}
		}

		/// <summary>
		/// Gets list of windows
		/// </summary>
		[XmlIgnore]
		internal Dictionary<IntPtr, DesktopWindow> Windows
		{
			get
			{
				return this.m_windows;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		[XmlIgnore]
		public Keys Hotkey
		{
			get
			{
				return this.m_hotkey;
			}
		}

		/// <summary>
		/// Gets or sets wallpaper
		/// </summary>
		[XmlAttribute]
		public string Wallpaper
		{
			get
			{
				return this.m_wallpaper;
			}
			set
			{
				this.m_wallpaper = value;
			}
		}

		/// <summary>
		/// Gets or sets tag
		/// </summary>
		[XmlIgnore]
		public object Tag
		{
			get
			{
				return this.m_tag;
			}
			set
			{
				this.m_tag = value;
			}
		}

		/// <summary>
		/// Gets name in "DesktopName (WindowsCount)" format
		/// </summary>
		[XmlIgnore]
		public string LongName
		{
			get
			{
				return string.Format("{0} ({1})", this.m_name, this.m_windows.Count);
			}
		}

		/// <summary>
		/// Gets name of this desktop
		/// </summary>
		[XmlAttribute]
		public string Name
		{
			set
			{
				this.m_name = value;
			}
			get
			{
				return this.m_name;
			}
		}

		/// <summary>
		/// Gets count of all processes that contains this desktop
		/// </summary>
		[XmlIgnore]
		public int WindowsCount
		{
			get
			{
				return this.m_windows.Count;
			}
		}

		#endregion Properties

		#region Methods

		/// <summary>
		/// 
		/// </summary>
		/// <param name="num"></param>
		/// <returns></returns>
		private double Resize(int what, int to)
		{
			return (double)to / (double)what;
		}

		/// <summary>
		/// Creates thumbnail (contains window postitions etc)
		/// </summary>
		private void CreateThumbnail()
		{
			// Get current resolution
			int height = Screen.PrimaryScreen.Bounds.Height;
			int width = Screen.PrimaryScreen.Bounds.Width;
			double p = this.Resize(width, 100);
			width = 100;
			height = (int)(height * p);

			if(this.m_thumbnail == null)
			{
				this.m_thumbnail = new Bitmap(width, height);
			}
			Graphics g = Graphics.FromImage(this.m_thumbnail);
			// Lets draw!
			g.Clear(SystemColors.AppWorkspace);
			Pen pen = new Pen(SystemColors.Desktop, 1);
			foreach (DesktopWindow window in this.m_windows.Values)
			{
				int x = (int)(window.Location.X * p);
				int y = (int)(window.Location.Y * p);
				int w = (int)(window.Size.Width * p);
				int h = (int)(window.Size.Height * p);
				Rectangle rect = new Rectangle(x, y, w, h);
				g.FillRectangle(Brushes.White, rect);
				g.DrawRectangle(pen, rect);
			}
			
			// this.m_thumbnail.Save(string.Format(@"c:\desktop_{0}.png", this.m_number));
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		private static string GetCurrentWallpaper()
		{
			RegistryKey wallKey = Registry.CurrentUser.OpenSubKey(@"Control Panel\Desktop", false);
			string path = wallKey.GetValue("WallPaper").ToString().Trim();
			wallKey.Close();
			return path;
		}

		/// <summary>
		/// Checks if this desktop containst windows explorer window with hwnd <see cref="hWnd"/>
		/// </summary>
		/// <param name="hWnd"><see cref="IntPtr"/></param>
		/// <returns>True if is already here</returns>
		private bool ContainsWindow(IntPtr hWnd)
		{
			return this.m_windows.ContainsKey(hWnd);
		}

		/// <summary>
		/// Static method that handle in <paramref name="hWnd"/> represents a window
		/// </summary>
		/// <param name="hWnd"></param>
		/// <returns></returns>
		internal static bool WindowCondition(IntPtr hWnd)
		{
			return (Desktop.IsWindow(hWnd) && Desktop.IsWindowVisible(hWnd) && Desktop.GetWindowTextLength(hWnd) > 0);
		}

		internal void ReleaseProcess(string processName)
		{
			DesktopWindow fake = new DesktopWindow(processName, IntPtr.Zero, this);
			List<IntPtr> dwptrs = new List<IntPtr>();
			foreach (IntPtr ptr in this.m_windows.Keys)
			{
				DesktopWindow dw = this.m_windows[ptr];
				if (dw.ProcessName == processName)
				{
					dwptrs.Add(ptr);
					dw.Show();
				}
			}
			if (dwptrs.Count > 0)
			{
				foreach (IntPtr ptr in dwptrs)
				{
					this.m_windows.Remove(ptr);
				}
				if (this.PropertiesChanged != null)
				{
					this.PropertiesChanged(this, EventArgs.Empty);
				}
			}
		}

		/// <summary>
		/// This method is called by <strong>EnumThreadWindows</strong>
		/// </summary>
		/// <param name="hwnd">Pointer (handle) to the process (Window) that was being analyzed</param>
		/// <param name="lParam">Dunno what it is :)</param>
		/// <returns>If you want to stop enumerating, just return false, otherwise true.</returns>
		private bool EnumerateWindows(IntPtr hWnd, IntPtr lParam)
		{
			if (hWnd == IntPtr.Zero || hWnd == null)
				return true;

			if (Desktop.WindowCondition(hWnd)) // Is the window ok?
			{
				StringBuilder sb = new StringBuilder(512);
				Desktop.GetWindowText(hWnd, sb, 512);
				string pn = sb.ToString().Trim().ToLower();

				// TODO:
				if (pn == "program manager") // This is realy..realy ugly solution. Bleugh!!1
				{
					return true;
				}

				bool isThere = false;
				foreach (Desktop desktop in DesktopManager.Instance.Desktops)
				{
					isThere = desktop.ContainsWindow(hWnd);
					if (isThere) // Is there?
					{
						return true;
					}
				}
				if (!isThere) // Nope
				{
					Debug.WriteLine(string.Format("Collecting.. {0}", pn));
					// Create new instance 
					DesktopWindow explorerWindow = new DesktopWindow(this.m_currentProcess, hWnd, this); 
					explorerWindow.Name = pn;

					this.m_windows.Add(hWnd, explorerWindow); // .. and add it.
				}
			}
			return true;
		}

		/// <summary>
		/// Method remove exited processes from <see cref="m_processes"/> and also add new ones.
		/// </summary>
		internal void Update()
		{
			// This desktop must be active to scan new processes
			if (this != DesktopManager.Instance.ActiveDesktop)
			{
				return;
			}
			int before = this.m_windows.Count;

			IntPtr[] keys = new IntPtr[this.m_windows.Keys.Count]; // Temporary field of keys (main module names)
			this.m_windows.Keys.CopyTo(keys, 0); // Copy to array

			foreach (IntPtr key in keys) // Lets check if processes are alive
			{
				DesktopWindow dp = this.m_windows[key];
				if (!dp.CheckAlive()) // Nope, this one is dead
				{
					this.m_windows.Remove(key); // Frak with it!
				}
			}

			Process[] processes = Process.GetProcesses(); // Get all processes
			foreach (Process process in processes) // We'll examinate each one
			{
				if (process.MainWindowHandle == IntPtr.Zero || process.MainWindowHandle == null)
				{
					continue;
				}
				if ((DesktopManager.Instance.ShareFolders && process.MainModule != null &&
					process.MainModule.ModuleName.ToLower().Trim() == "explorer.exe"))
				{
					continue;
				}
				try
				{
					if (DesktopManager.Instance.ContainsShared(process.MainModule.ModuleName.Trim()))
					{
						continue;
					}
				}
				catch (Win32Exception ex)
				{
					Debug.Write(ex);
					continue;
				}

				// Temporaily save process name to member 
				this.m_currentProcess = process.MainModule.ModuleName.Trim();
				foreach (ProcessThread pt in process.Threads)
				{
					this.m_deleg = new EnumThreadDelegate(this.EnumerateWindows);
					Desktop.EnumThreadWindows((uint)pt.Id, this.m_deleg, IntPtr.Zero);
				}
				this.m_currentProcess = string.Empty; // Empty
			}

			this.CreateThumbnail();

			if (before != this.m_windows.Count && this.PropertiesChanged != null)
			{
				this.PropertiesChanged(this, EventArgs.Empty);
			}
		}

		/// <summary>
		/// Hides all processes that belongs to this desktop
		/// </summary>
		internal void Hide()
		{
			foreach (DesktopWindow ew in this.m_windows.Values)
			{
				ew.Hide();
			}
		}

		/// <summary>
		/// Shows all processes that belongs to this desktop
		/// </summary>
		internal void Show()
		{
			// If the wallpaper's set correctly..
			if (this.m_wallpaper.Trim().Length > 0 && File.Exists(this.m_wallpaper) && 
				_currentWallpaper != this.m_wallpaper) // Current wallpaper must be different..
			{ // Lets chagne it! :D
				MethodInvoker invoker = delegate
				{
					Desktop.SystemParametersInfo(20, 0, this.m_wallpaper, 0x1 | 0x2);
					_currentWallpaper = this.m_wallpaper;
				};
				invoker.BeginInvoke(null, null);
			}

			// For each window in this dekstop
			foreach (DesktopWindow ew in this.m_windows.Values)
			{
				ew.Show(); // Show!
			}

			if (this.Activated != null)
			{
				this.Activated(this, EventArgs.Empty);
			}
		}

		#endregion Methods

		#region Events

		/// <summary>
		/// Occurs when some of properties had changed (for example the count of windows)
		/// </summary>
		public event System.EventHandler PropertiesChanged;

		/// <summary>
		/// 
		/// </summary>
		public event System.EventHandler Activated;

		#endregion Events

		#region Event Reactions

		void HotKeyWrapper_HotKeyPressed(object sender, KeyEventArgs e)
		{
			if (e.KeyData == this.m_hotkey)
			{
				DesktopManager.Instance.Show(this);
			}
		}

		#endregion Event Reactions

	}
}
